<?php

1->现有配置分析
   -) '弊端'
       . 格式不统一 (json / properties / yaml / yml)
       . 没有版本控制 (大家随心所欲, 想改就改)
       . 基于静态配置 (写在代码里, 发布流程繁琐)
       . 分布零散 (没有统一管理)

    -) '配置管理需求'
                                              +-----------+
                                              |  配置项定义 |
                                              +-----------+
                                                    |
                +-----------------------+------------------------+----------------------+
                |                       |                        |                      |
              高可用                  版本管理                   业务需求            配置分离, 中心化管理
                                        |                        |
                                  +-----------+            +-----------+
                                  |           |            |           |
                               修改记录  版本控制/权限控制  内容加密    动态推送变更



2->Config核心功能与原理
   -) '配置前世今生'
       随着技术的发展，配置项管理变得越来越简单，尽管如今它只限于管理业务属性或者配置初始化参数等等，但是当年它可肩负着Spring IOC的光荣使命，风光无限。

       很早之前还是SSH (Struts + Spring + Hibernate)的天下，那时远没有如今这些丰富的开源组件，一个标准的Java程序员就是靠SSH这三把刷子打天下，
       正所谓学好数理化，走遍天下都不怕，但那时候的配置管理是一件让人头疼的事情。

       早先SSH的配置管理可以说是相当复杂，每个项目的XML配置文件大大小小几十个。如果有过Struts1的使用经验就能理解这种痛苦，
       创建一个Form表单从前到后要创建好几个Java Class，还要再加上一大堆配置文件。那时的Spring还没有@Autowired功能，
       所有类之间的依赖关系全部靠XML来描述，远不像今天一个auto-scan搞定一切。所以，配置管理承担了Spring的依赖注入功能。

   -) '第一次工业革命'
       这一次由Spring引领(带头大哥)，在Spring引入自动装配机制以后，情况发生了天翻地覆的变化。基于注解的自动装配取代了繁重的XML配置，
       使用@Autowired，@Qualifier，@Primary等一系列自动装配注解，把所有关于“依赖注入”的繁重工作从配置项中解放了出来。

       所以说现在的项目已经很少看到XML配置的身影了，配置文件只要关注业务属性的管理，比如说注册中心的连接参数，或者某个功能开关的控制等等.
       变化: 将传统'XML配置', 解放到了'自动装配'.

   -) '第二次工业革命'
       随着应用复杂度的提升，大家开始意识到使用工程化的方式对待杂乱的配置项.
       . [环境隔离]
          当项目中建立起了多级环境（从测试、集成、上线都有独立的研发环境），需要我们的应用根据不同环境加载不同的配置文件，把各个不同环境的配置项隔离开来，
          把配置文件也纳入工程化部署的一部分.

       . [运行期变更]
          大多数配置文件的内容是静态属性，也就是说自打项目启动加载一次以后就再也不会发生变化，可随着业务复杂度提升，
          我们也需要一种能够在应用运行期修改属性的功能，以支持复杂业务场景.

          在这个阶段各种技术百花齐放，每个公司都有自己的玩法，就拿第二点来说，有些公司直接把需要动态变更的属性放到数据库中或者缓存中，
          每次运行的时候去读取一把，但是这并不是一种既方便又高效的解决方案。那么我们有没有一个简单、高效并且还易于管理的方式可以应用在配置项上呢？



3->Config—Server(配置服务端)
#  假舆马者，非利足也，而致千里；假舟楫者，非能水也，而绝江河。君子生非异也，善假于物也.
#                                                                       - 荀子·劝学
   -) '功能'
       Config作为Spring Cloud官方指定产品，在配置管理方面主要提供了三个功能.
                                 +-------------+
                                 |   统一配置   |
                                 +-------------+
                                        |
                                   +---------+
                                   |  Config |
                                   +---------+
                                        |
                              +-----------------+
                              |                 |
                           环境隔离            动态刷新

      统一配置: 提供了一个中心化的配置方案，将各个项目中的配置内容集中在Config Server一端.
      环境隔离: Config Server提供了多种环境隔离机制，Client可以根据自身所处的项目环境（比如生产环境、测试环境）加载对应的配置文件.
      动态刷新: 支持运行期改变配置属性.

   -) '实现原理'
       Config-Server负责'动态拉取-环境仓库'上的配置属性, 然后'等待所有微服务调用获取'.

                                      自动装配
                                         |
                              环境仓库装配 (JDBC/SVN/GitHub/Native)
                                         |
                                    对外REST接口
                                         |
                            +------------+------------+
                            |            |            |
                          服务A         服务B         服务...

       自动装配:
       秉承了Spring Cloud的一贯传统，一段故事从一个注解开始。我们只要在启动类上放个@EnableConfigServer注解就可以了，
       Config内部再通过@Import注解引入其他配置类，完成自动装配（具体承担自动装配的类是ConfigServerAutoConfiguration）

       环境仓库装配:
       启动了自动装配以后，自然要知道从哪里拉取配置文件，具体负责环境仓库装配的是EnvironmentRepositoryConfiguration这个类。
       Config Server支持很多种文件存储仓库，比如JDBC，SVN，GitHub和本地文件，当然也可以配置多种类型组合的方式，
       也就是说Config会从不同的地方拉取配置文件。默认情况下Config采用基于'GitHub'的集成方式，这也是官方建议的方式.

       对外REST接口:
       从外部环境拉取到了配置文件之后，需要返回给客户端。Config通过EnvironmentController这个类对外提供了一套供客户端调用的REST格式接口，
       所有服务都是通过GET方法对外提供，客户端可以通过不同的URL路径获取相对应的配置内容.
       (除此之外，Config还提供了属性转换的功能。假如我们提供的配置文件是yml形式的，如果希望获取其他格式的配置项，那么在调用第三步中的REST接口时
       可以在URL后面以扩展名结尾，比如“.json”或者”.properties“，Config Server会自动将配置内容转为对应格式。)



4->Config—Client(配置客户端)
   -) '实现原理'
       如果大家在application.yml中定义了一个属性test，并使用占位符${remoteTest}作为test属性的值,
       当我们在Config Server中配置remoteTest属性后, 你会发现在项目完成启动的时候，Config Server中的remoteTest被注入到了配置文件中的test属性。
       从这个现象我们可以得出一个结论，应用程序一定是在Spring上下文'初始化的早期阶段'就从Config Server获取了配置文件，这个过程'优先于本地配置项的加载过程'.

       补充: Config的配置我们会放在bootstrap.yml. (application.properties的加载优先级 > application.yml)

   -) '应用初始化方式'
                                                       +--------------------------------+
                                                       |                                |
                                          +------------+------------+                   |
                                          | SpringBoot Context初始化 |                   |
                                          +------------+------------+                   |
                                                       |                 [SpringBooot]  |
                                            +----------+----------+                     |
                                            |    加载initializer   |                     |
                                            +----------+----------+                     |
                                                       |                                |
                                                       +--------------------------------+
                                                       |
                        +------------------------------+
                        |                              |
                        |                     +--------+--------+
                        |                     |   属性资源初始化  |
                        |                     +--------+--------+
                        |    [SpringCloud]             |
                        |                              |
                        |                     +--------+--------+
                        |                     |    拉取远程文件   |
                        |                     +--------+--------+
                        +------------------------------|

       SpringCloud应用同时也是一个SpringBoot应用，因此整个应用的初始化从SpringBoot启动时的上下文Context构建开始:
       . 'SpringBoot构建Context'
         和所有的SpringBoot项目一样，通过SpringApplication类的run方法开始启动项目初始化和加载流程，其中就包括prepareContext这一步，
         整个项目的上下文结构就通过这个方法来构建.

       . '加载initializer'
         这是一连串的初始化构造过程，当我们在项目中引入了SpringCloud依赖时，PropertySourceBootstrapConfiguration将作为一个初始化构造器，
         参与SpringBoot上下文的初始化，用来加载SpringCloud的属性资源.

       . '初始化属性资源'
         PropertySourceBootstrapConfiguration是通过一系列的locator来定位资源文件的，
         当我们在项目中引入springcloud-config-client的依赖以后, 就会开启Config组件的自动装配 (由ConfigServiceBootstrapConfiguration实现),
         在这个自动装配过程中会向locator列表里添加一个专门用来获取远程文件的类-ConfigServicePropertySourceLocator.

       . '拉取远程文件'
         ConfigServicePropertySourceLocator定义了执行顺序的优先级是0（通过@Order(0)注解定义），在Spring中这个数字越小则表示优先级越高，因此，
         这个组件将优先于其他locator先被执行。通过getRemoteEnvironment方法，向Config Server发起请求，获取远程属性.


5->配置中心————直接式
   -) '架构'
                          Config   ------------> GitHub
                            |
                 +----------+----------+
                 |                     |
              Server1               Server2

      GitHub: 存储所有的配置项信息.
      Config: config组件, 从GitHub获取资源文件, 并保存一份在本地.
      Server...: 众多服务节点请求获取配置.

   -) '工作模式'
       - 生死相随 (广听言论)
         此模式下, Config'只会'从GitHub或者数据库获取配置信息;
         . 'GitHub'
            如果Github是公共仓库，那就不需要配置用户名和密码。
            如果是私有仓库，要把登录用的用户名和密码写到配置文件里面，当然密码可以通过加密方式存储，然后系统启动的时候使用密钥进行解密.
            配置: spring.cloud.config.server.git.uri=https://github.com/xxxxx/config-repo.git
            在企业应用中，有时也会有多个项目共同使用一个Github repo的情况，这时候就需要将不同项目的资源文件放到不同目录下，使用如下配置，
            给你的服务指定一个独立的目录存放配置文件
            指定文件夹: spring.cloud.config.server.git.search-paths=/{appName}

         . '数据库'
            需要额外指定'spring.profiles.active=jdbc'切换到db方案，并指定数据源.

       - 自立门户 (不听不听-王八念经)
         不屑从GitHub拉取配置信息, 并且一脚踢开了她, config组件自己说了算! 直接从本地读取资源文件.
         采用此方式, 需要指定'spring.profiles.active=native'开启此功能, 然后指定本地文件存储路径.(服务器上的路径/project/config/files/xxx.yml)

   -) '直接方式利弊'
       . 好处: 从部署结构上来讲相当简单，组件间依赖也少.

       . 弊端: 可用性不能得到保障，假如Config节点宕机会产生较大影响。由于需要将在客户端启动的时候指定Config地址，即便这里使用DNS地址（比如域名等非IP形式），
              如果域名发生变化仍然需要在客户端重新配置Config地址。


6->创建配置拉取之源————GitHub
   -) '文件命名规则 - 标识后面文件拉取!'
       Application & Profile
       {applicationName}-{profile}.yml
       {applicationName}-{profile}.properties
       # user-info-dev.yml

       配置扫描目录: spring.cloud.config.server.git.search-paths={folderName}
       默认拉取分支: master


7->创建ConfigServer
   -) 'pom'
       <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-config-server</artifactId>
          </dependency>
       </dependencies>

   -) '注解'
       @SpringBootApplication
       @EnableConfigServer

   -) '配置文件'
       spring:
          application:
            name: config-server
          cloud:
            config:
              server:
                git:
                  uri: https://github.com/18616860694/springcloud-conf-repo.git
                  force-pull: true                # 是否【强制拉取】，默认false
          #          search-paths: xm,dev           # 指定【文件夹扫描路径】，会从【uri / xm / dev】三个地方进行文件拉取
          #          default-label: main            # 指定从【哪个分支】拉取，默认master
                   # 如果创建的仓库为【私有仓库】，那么这里需要配置【用户名 / 密码】，其中密码可以加密。
          #          username:
          #          password:

   -) '拉取'
       - '格式1'
          localhost:port/{applicationName}/{profile}/{label}
          localhost:9001/config-consumer/dev/master

       - '格式2-推荐'
          localhost:port/{label}/{applicationName}-{profile}.yml (.json / .properties)
          localhost:9001/master/config-consumer-dev.yml
          # 如果默认[master分支]    localhost:9001/config-consumer-dev.yml
          (properties / json, 格式会自动转 ^=^, 只是展示格式转换, 拉取下来的文件依旧是仓库里的类型!)

      注意: 如果没有该分支名称的话会报: 404

      此时会在本地保存一份拉取下来的配置文件:
      Adding property source: file:/var/folders/r0/ysyjc3pj18n83n6sbr_k97500000gn/T/config-repo-8119943099315638521/config-consumer-dev.yml
      但是: 存储本地的文件, 是服务器上的类型 (.yml), 并且'重复拉取'只会'覆盖'.



8->创建ConfigClient
   -) 'pom'
       <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
       </dependencies>

   -) '注解'
       @SpringBootApplication

   -) '配置文件'
       spring:
        application:
          name: config-client
        cloud:
          config:
            uri: http://localhost:9001    # 指定【config-server】端口
      #      name: config-consumer        # 当 项目名 与 Gitee上面的文件服务名 不一致时, 可以配置使之保持一致.
            profile: prod                 # 指定读取文件的【环境】，一般是：外部指定启动环境

       name: 小木          # 直接读取
       myWords: ${words}  # 先读取, 再加载

       @Value("${name}")
       @Value("${myWords}")


   -) '核心'
       . 加载使用配置中心一定要是'bootstrap', 一般的'application'会报错!!!

       . 代码拉取配置文件有2种方式
         - '直接拉取'配置中心的属性.     (代码加载 <----> config-server)
         - 配置文件定义变量, 拉取并使用.  (配置文件变量 <----> 代码加载 <----> config-server)

       . 配置仓库上面是以'{applicationName}-{profile}'来定义文件的, 所以在拉取'config-server'上的配置时,
         需要保持'项目名称 = 配置文件名称'! 当然, 此名称也可以'手动指定':

       . 以下配置的话, 最终'本地变量'会覆盖'拉取到的属性值', 最终值为: words of xm...
         words: words of xm...
         myWords: ${words}

       . 如果开启了'获取配置中心设置', 那么可以'直接获取配置中心属性', 并且该属性可'不在本地声明!'
         例如: 服务A连接了配置中心, 此时配置中心上有属性'name', 那么即使服务A不在配置文件里引用name (name: ${name}),
         依旧可以直接进行注入:
         @Value("${name}")
         private String name;

         原理: 项目启动会先去拉取'配置中心'的配置, 检测是否存在'注入的变量名'!
              此时如果没有该变量, 那么项目启动就会报错!


9->现在配置存在的问题
   -) '问题'
       我们虽然从'仓库拉取配置文件', 但是, 如果仓库中的配置文件'发生变化'了, 我们怎么办? 总不能还一直用'本地文件'吧?!

   -) '架构'
       actuator/refresh(服务) -------发送刷新请求(POST)-----> Config ------------> GitHub

       . 发送刷新请求: 我们选定一个服务节点，通过POST请求访问节点下的'/actuator/refresh'路径，这时节点会发送一个刷新请求到Config服务器.
       . 拉取文件: Config服务器会访问Github获取最新的内容，并把配置信息文件下载到本地.
       . 获取更新内容: 接着服务节点从Config那里拿到变更内容，并将变动的属性配置到各个类中.

       在第三步骤里还有个小前提，假如一个类中有需要进行运行期替换操作的属性，那就要把'@RefreshScope注解'加到这个类上, 这样运行期参数修改才会在这个类上面生效.

   -) 'Actuator'
       (https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/actuator-api//html/)
       Actuator是一个轻巧的监控组件，通过REST接口的方式可以供外部调用，访问服务节点下的'/actuator'路径可以查看当前开放的服务.

       Actuator也是一个相当贴心的组件，当你引入Spring Cloud的其他组件依赖到pom中以后（比如Config或BUS），这部分组件会通过Actuator Endpoint
       将自己的核心服务提供出去（比如Config和BUS的refresh功能）.

       假如访问'/actuator'后只能看到health和info这两个服务，那是因为Actuator的服务包含很多“机密”信息，为了安全考虑，默认只暴露几个无关痛痒的接口。
       我们可以通过改动默认配置项让它暴露指定的接口，为了方便演示，本课程中我们采用以下配置让Actuator暴露所有服务，
       这样你就可以通过'/actuator'路径查看当前可用的服务了。
       开启服务: management.endpoints.web.exposure.include=*



10->动态刷新
#   贵有恒何必三更起五更睡，最无益只怕一日曝十日寒.
    -) '问题原因'
        config-client 动态获取 config-server 同步过来的值. 因为GitHub的配置文件更新的话, config-server会'动态获取'到值, 然后'保存本地文件',
        问题出就出在'保存本地文件'这里, 因为config-client会读取'本地文件', 那么即使config-server获取到'最新配置文件', config-client也会继续读取'本地文件'!
        除非'重启config-client', 那么我们要解决的就是'如何不重启'就可以'动态获取配置文件'!

    -) 'pom'
        <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-config</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
       </dependencies>

    -) '注解'
        @RefreshScope
        说明: 打在想要刷新的类上, 如果类'没有打此注解'的话, 那么就'不会动态刷新'.

    -) '配置文件'
        management:
          security:
            enabled: false
          endpoints:
            web:
              exposure:
                include: '*'      # 实际上我们可以根据实际需求, 开放对外暴露端点!
          endpoint:
            health:
              show-details: always

    -) '原理'
        @RefreshScope的类会'重新注入到context上下文中', 然后发起 [config-client ---> config-server ---> GitHub] 去同步最新配置.


11->配置文件
    config-server | config-client
    由于是需要在项目启动前就去拉取'配置信息', 所以要放在'bootstrap.yml'中!!!
